#ifndef B2D_EVENT_H
#define B2D_EVENT_H

#include "Prerequisites.h"
#include "FastDelegate.h"
#include <vector>

////////////////////////////////////////////////////////////////////////////////////////////////////
///	Original work from Torque2D
///	Modified to comply with b2d
////////////////////////////////////////////////////////////////////////////////////////////////////

NS_B2D_BEGIN

////////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief	A macro that defines a delegate.
////////////////////////////////////////////////////////////////////////////////////////////////////
#define Delegate fastdelegate::FastDelegate

////////////////////////////////////////////////////////////////////////////////////////////////////
/// \brief	An opaque structure which can hold an arbitary delegate.
////////////////////////////////////////////////////////////////////////////////////////////////////
typedef fastdelegate::DelegateMemento DelegateMemento;


template<typename T>
class B2D_API DelegateRemapper : public DelegateMemento
{
protected:
   int mOffset;

public:
   DelegateRemapper(void) : mOffset(0) {}

	void set(T* t, const DelegateMemento& memento)
	{
		SetMementoFrom(memento);
		if(m_pthis != nullptr)
			mOffset = ((int)m_pthis) - ((int)t);
	}

	void rethis(T* t)
	{
		if (m_pthis)
			m_pthis = reinterpret_cast<fastdelegate::detail::GenericClass*>(mOffset + (int)t);
	}
};

///
/// Event class
/// You can subscribe to this event using event handlers
///
class B2D_API EventBase
{
protected:
	struct DelegateLink
	{
		DelegateLink *next,*prev;
		float order;

		void insert(DelegateLink* node, float order)
		{
			// Note: can only legitimately be called on list head
			DelegateLink * walk = next;
			while (order >= walk->order && walk->next != this)
				walk = walk->next;
			if (order >= walk->order)
			{
				// insert after walk
				node->prev = walk;
				node->next = walk->next;
				walk->next->prev = node;
				walk->next = node;
			}
			else
			{
				// insert before walk
				node->prev = walk->prev;
				node->next = walk;
				walk->prev->next = node;
				walk->prev = node;
			}
			node->order = order;
		}

		void unlink(void)
		{
			// Unlink this node
			next->prev = prev;
			prev->next = next;
		}
	};

   DelegateLink mList;

   /// We need to protect the delegate list against removal of the currently
   /// triggering node as well removal of the next node in the list.  When not
   /// handling these two cases correctly, removing delegates from a signal
   /// while it is triggering will lead to crashes.
   ///
   /// So this field stores the next node of each active traversal so that when
   /// we unlink a node, we can check it against this field and move the traversal
   /// along if needed.
   std::vector<DelegateLink*> mTriggerNext;

public:
	virtual ~EventBase(void)
	{
		clearDelegates();
	}

	/// removeListeners all the delegates from the event
	void clearDelegates(void)
	{
		while (mList.next != &mList)
		{
			DelegateLink* ptr = mList.next;
			ptr->unlink();
			delete ptr;
		}
	}
	/// Check if the delegate list is empty
	bool isEmpty(void) const { return mList.next == &mList; }

protected:
	EventBase(void)
	{
		mList.next = mList.prev = &mList;
		mList.order = 0.5f;
	}
};

template<typename Signature>
class B2D_API EventBaseT
	: public EventBase
{
public:
	typedef Delegate<Signature> DelegateSig;

protected:
	struct DelegateLinkImpl : public EventBase::DelegateLink
	{
		DelegateSig mDelegate;
		DelegateLinkImpl(DelegateSig dlg) : mDelegate(dlg) {}
	};

public:
	EventBaseT(void) {}
	EventBaseT(const EventBaseT& base)
	{
		mList.next = mList.prev = &mList;
		merge( base );
	}

	void operator =(const EventBaseT& base)
	{
		clearDelegates();
		merge( base );
	}

	void merge(const EventBaseT& base)
	{
		for ( DelegateLink *ptr = base.mList.next; ptr != &base.mList; ptr = ptr->next )
		{
			DelegateLinkImpl *del = static_cast<DelegateLinkImpl*>( ptr );
			addListener( del->mDelegate, del->order );
		}
	}

	void addListener( const DelegateSig &dlg, float order = 0.5f)
	{
		mList.insert(new DelegateLinkImpl(dlg), order);
	}
	template <typename T ,typename U>
	void addListener(T obj,U func, float order = 0.5f)
	{
		DelegateSig dlg(obj, func);
		addListener(dlg, order);
	}
	template <typename T>
	void addListener(T func, float order = 0.5f)
	{
		DelegateSig dlg(func);
		addListener(dlg, order);
	}

	void removeListener(DelegateSig dlg)
	{
		for( DelegateLink* ptr = mList.next;ptr != &mList; ptr = ptr->next )
		{
			if( DelegateLinkImpl* del = static_cast< DelegateLinkImpl* >( ptr ) )
			{
				if( del->mDelegate == dlg )
				{
					for ( int i = 0; i < mTriggerNext.size(); i++ )
					{
						if( mTriggerNext[i] == ptr )
							mTriggerNext[i] = ptr->next;
					}

					del->unlink();
					delete del;
					return;
				}
			}
		}
	} 
	template <typename T ,typename U>
	void removeListener(T obj,U func)
	{
		DelegateSig compDelegate(obj, func);
		removeListener(compDelegate);
	}
	template <typename T>
	void removeListener(T func)
	{
		DelegateSig compDelegate(func);
		removeListener(compDelegate);
	} 

	/// Returns true if the signal already contains this delegate.
	bool contains(const DelegateSig &dlg) const
	{
		for (DelegateLink *ptr = mList.next; ptr != &mList; ptr = ptr->next)
		{
			DelegateLinkImpl *del = static_cast<DelegateLinkImpl*>(ptr);
			if ( del->mDelegate == dlg )
			return true;
		}
		return false;
	}

protected:
	DelegateSig& getDelegate(EventBase::DelegateLink* link)
	{
		return ((DelegateLinkImpl*)link)->mDelegate;
	}
};

//-----------------------------------------------------------------------------

template<typename Signature> class B2D_API Event;

// Short-circuit signal implementations
template<> 
class B2D_API Event<bool()> : public EventBaseT<bool()>
{
   public:

      bool operator() ()
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            if( !this->getDelegate( ptr )() )
            {
               this->mTriggerNext.pop_back();
               return false;
            }
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
         return true;
      }
};

template<typename A> 
class B2D_API Event<bool(A)> : public EventBaseT<bool(A)>
{   
   public:

      bool operator() ( A a )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            if( !this->getDelegate( ptr )( a ) )
            {
               this->mTriggerNext.pop_back();
               return false;
            }
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
         return true;
      }
};

template<typename A, typename B>
class B2D_API Event<bool(A,B)> : public EventBaseT<bool(A,B)>
{
   public:

      bool operator() ( A a, B b )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            if( !this->getDelegate( ptr )( a, b ) )
            {
               this->mTriggerNext.pop_back();
               return false;
            }
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
         return true;
      }
};

template<typename A, typename B, typename C> 
class B2D_API Event<bool(A,B,C)> : public EventBaseT<bool(A,B,C)>
{
   public:

      bool operator() ( A a, B b, C c )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            if( !this->getDelegate( ptr )( a, b, c ) )
            {
               this->mTriggerNext.pop_back();
               return false;
            }
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
         return true;
      }
};

template<typename A, typename B, typename C, typename D> 
class B2D_API Event<bool(A,B,C,D)> : public EventBaseT<bool(A,B,C,D)>
{
   public:

      bool operator() ( A a, B b, C c, D d )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            if( !this->getDelegate( ptr )( a, b, c, d ) )
            {
               this->mTriggerNext.pop_back();
               return false;
            }
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
         return true;
      }
};

template<typename A, typename B, typename C, typename D, typename E> 
class B2D_API Event<bool(A,B,C,D,E)> : public EventBaseT<bool(A,B,C,D,E)>
{
   public:

      bool operator() ( A a, B b, C c, D d, E e )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            if( !this->getDelegate( ptr )( a, b, c, d, e ) )
            {
               this->mTriggerNext.pop_back();
               return false;
            }
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
         return true;
      }
};

template<typename A, typename B, typename C, typename D, typename E, typename F> 
class B2D_API Event<bool(A,B,C,D,E,F)> : public EventBaseT<bool(A,B,C,D,E,F)>
{
   public:

      bool operator() ( A a, B b, C c, D d, E e, F f )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            if( !this->getDelegate( ptr )( a, b, c, d, e, f ) )
            {
               this->mTriggerNext.pop_back();
               return false;
            }
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
         return true;
      }
};

template<typename A, typename B, typename C, typename D, typename E, typename F, typename G> 
class B2D_API Event<bool(A,B,C,D,E,F,G)> : public EventBaseT<bool(A,B,C,D,E,F,G)>
{
   public:

      bool operator() ( A a, B b, C c, D d, E e, F f, G g )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            if( !this->getDelegate( ptr )( a, b, c, d, e, f, g ) )
            {
               this->mTriggerNext.pop_back();
               return false;
            }
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
         return true;
      }
};

template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H> 
class B2D_API Event<bool(A,B,C,D,E,F,G,H)> : public EventBaseT<bool(A,B,C,D,E,F,G,H)>
{
   public:

      bool operator() ( A a, B b, C c, D d, E e, F f, G g, H h )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            if( !this->getDelegate( ptr )( a, b, c, d, e, f, g, h ) )
            {
               this->mTriggerNext.pop_back();
               return false;
            }
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
         return true;
      }
};

template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I> 
class B2D_API Event<bool(A,B,C,D,E,F,G,H,I)> : public EventBaseT<bool(A,B,C,D,E,F,G,H,I)>
{
   public:

      bool operator() ( A a, B b, C c, D d, E e, F f, G g, H h, I i )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            if( !this->getDelegate( ptr )( a, b, c, d, e, f, g, h, i ) )
            {
               this->mTriggerNext.pop_back();
               return false;
            }
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
         return true;
      }
};

template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J> 
class B2D_API Event<bool(A,B,C,D,E,F,G,H,I,J)> : public EventBaseT<bool(A,B,C,D,E,F,G,H,I,J)>
{
   public:

      bool operator() ( A a, B b, C c, D d, E e, F f, G g, H h, I i, J j )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            if( !this->getDelegate( ptr )( a, b, c, d, e, f, g, h, i, j ) )
            {
               this->mTriggerNext.pop_back();
               return false;
            }
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
         return true;
      }
};

// Non short-circuit signal implementations

template<> 
class B2D_API Event<void()> : public EventBaseT<void()>
{
   public:

      void operator() ()
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            this->getDelegate( ptr )();
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
      }
};

template<typename A>
class B2D_API Event<void(A)> : public EventBaseT<void(A)>
{
   public:

      void operator() ( A a )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            this->getDelegate( ptr )( a );
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
      }
};

template<typename A, typename B> 
class B2D_API Event<void(A,B)> : public EventBaseT<void(A,B)>
{
   public:

     void operator() ( A a, B b )
     {
        this->mTriggerNext.push_back(NULL);
        for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
        {
           this->mTriggerNext.back() = ptr->next;
           this->getDelegate( ptr )( a, b );
           ptr = this->mTriggerNext.back();
        }
        this->mTriggerNext.pop_back();
      }
};

template<typename A, typename B, typename C> 
class B2D_API Event<void(A,B,C)> : public EventBaseT<void(A,B,C)>
{
   public:

      void operator() ( A a, B b, C c )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            this->getDelegate( ptr )( a, b, c );
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
      }
};

template<typename A, typename B, typename C, typename D>
class B2D_API Event<void(A,B,C,D)> : public EventBaseT<void(A,B,C,D)>
{
   public:

      void operator() ( A a, B b, C c, D d )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            this->getDelegate( ptr )( a, b, c, d );
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
      }
};

template<typename A, typename B, typename C, typename D, typename E> 
class B2D_API Event<void(A,B,C,D,E)> : public EventBaseT<void(A,B,C,D,E)>
{
   public:

      void operator() ( A a, B b, C c, D d, E e )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            this->getDelegate( ptr )( a, b, c, d, e );
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
      }
};

template<typename A, typename B, typename C, typename D, typename E, typename F> 
class B2D_API Event<void(A,B,C,D,E,F)> : public EventBaseT<void(A,B,C,D,E,F)>
{
   public:

      void operator() ( A a, B b, C c, D d, E e, F f )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            this->getDelegate( ptr )( a, b, c, d, e, f );
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
      }
};

template<typename A, typename B, typename C, typename D, typename E, typename F, typename G> 
class B2D_API Event<void(A,B,C,D,E,F,G)> : public EventBaseT<void(A,B,C,D,E,F,G)>
{
   public:

      void operator() ( A a, B b, C c, D d, E e, F f, G g )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            this->getDelegate( ptr )( a, b, c, d, e, f, g );
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
      }
};

template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H> 
class B2D_API Event<void(A,B,C,D,E,F,G,H)> : public EventBaseT<void(A,B,C,D,E,F,G,H)>
{
   public:

      void operator() ( A a, B b, C c, D d, E e, F f, G g, H h )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            this->getDelegate( ptr )( a, b, c, d, e, f, g, h );
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
      }
};

template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I> 
class B2D_API Event<void(A,B,C,D,E,F,G,H,I)> : public EventBaseT<void(A,B,C,D,E,F,G,H,I)>
{
   public:

      void operator() ( A a, B b, C c, D d, E e, F f, G g, H h, I i )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            this->getDelegate( ptr )( a, b, c, d, e, f, g, h, i );
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
      }
};

template<typename A, typename B, typename C, typename D, typename E, typename F, typename G, typename H, typename I, typename J> 
class B2D_API Event<void(A,B,C,D,E,F,G,H,I,J)> : public EventBaseT<void(A,B,C,D,E,F,G,H,I,J)>
{
   public:

      void operator() ( A a, B b, C c, D d, E e, F f, G g, H h, I i, J j )
      {
         this->mTriggerNext.push_back(NULL);
         for( EventBase::DelegateLink* ptr = this->mList.next; ptr != &this->mList; )
         {
            this->mTriggerNext.back() = ptr->next;
            this->getDelegate( ptr )( a, b, c, d, e, f, g, h, i, j );
            ptr = this->mTriggerNext.back();
         }
         this->mTriggerNext.pop_back();
      }
};

NS_B2D_END

#endif